#if !defined(PROGRESS_INDICATOR_H)
#define PROGRESS_INDICATOR_H
#include <iostream>
#include <algorithm>
#include "portable_timer.h"
#include <boost/format.hpp>
//#include "debug_func.h"
#include <cassert>
using std::ostream;
//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	t_progress_indicator

//		streams out progress dots every n iterations

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
class t_progress_indicator
{
public:
	unsigned			every;
	ostream&			os;
	unsigned			iteration;
	unsigned			iteration_in_interval;				// cope with different numbers of iterations in different interval
	unsigned			line_length;
	unsigned			limit;
	unsigned			count_dots;
	unsigned			count_letters;
	char				indicator;
	bool				using_timer;
	double				elapsed_time;						// in seconds
	unsigned			cnt_desired_dots;
	t_portable_timer	timer;
	// Before any dots are output, print out completion time estimate if greater than 
	//	threshold
	double				threshold_completion_time_estimate;
public:
	t_progress_indicator(ostream& os_, unsigned every_, char indicator_ = '.',
							unsigned line_length_ = 50):
		every(every_), os(os_), iteration(0), iteration_in_interval(0),
		line_length(line_length_), limit(0), count_dots(0), count_letters(0), indicator(indicator_),
		using_timer(false), elapsed_time(0.0), cnt_desired_dots(0), 
		threshold_completion_time_estimate(-1)
	{
		assert(line_length);
		if (!every)
			every++;
	}
	t_progress_indicator(ostream& os_, unsigned every_, unsigned limit_,
							char indicator_ = '.', unsigned line_length_ = 50):
		every(every_), os(os_), iteration(0), iteration_in_interval(0),
		line_length(line_length_), limit(limit_), count_dots(0), count_letters(0), indicator(indicator_),
		using_timer(false), elapsed_time(0.0), cnt_desired_dots(0), 
		threshold_completion_time_estimate(-1)
	{
		assert(line_length);
		if (!every)
			every++;
	}

	//
	//	calculates values given file size
	//
	t_progress_indicator(ostream& os_, 
							uint64_t file_size, 
							unsigned row_length_guess,
							unsigned cnt_desired_dots_, 
							char indicator_ = '.', 
							unsigned line_length_ = 50):
		os(os_), iteration(0), iteration_in_interval(0),
		line_length(line_length_), count_dots(0), count_letters(0), indicator(indicator_),
		using_timer(false), elapsed_time(0.0), cnt_desired_dots(cnt_desired_dots_), 
		threshold_completion_time_estimate(-1)
	{
		assert(line_length);
		assert(file_size);
		assert(row_length_guess);
		assert(cnt_desired_dots);

		limit			= file_size / row_length_guess;
		if (!limit)
			limit++;
		every			= limit / cnt_desired_dots;
		if (!every)
			every++;
	}
	//
	//	reestimate_parameters for how many dots are needed based on how much of the file 
	//	has been processed
	//
	void reestimate_parameters(	uint64_t file_size, 
								uint64_t bytes_read, 
								unsigned cnt_rows_read
								) 
	{
		using std::max;
		// total number of rows
		unsigned dots_remaining = max(cnt_desired_dots , 1 + count_dots) - count_dots;

		limit = unsigned(file_size * 1.0 / bytes_read * cnt_rows_read + 1);
		unsigned rows_remaining = max(limit , 1 + cnt_rows_read) - cnt_rows_read;
		every = max(rows_remaining / dots_remaining, 1U);
		if (!limit)
			limit++;
		if (!every)
			every++;

		//string s = (boost::format(" %1$d %2$d %3$d %4$d %5$d %6$d") 
		//			% dots_remaining 
		//			% rows_remaining
		//			% iteration
		//			% every
		//			% count_dots
		//			% limit
		//			).str();
		//for (unsigned ss = 0, ss_end = s.length(); ss < ss_end; ++ss)
		//	print_letter(s[ss]);
	}
	bool any_letters_printed() const {return count_letters;}
	void set_every(unsigned e) {every = e; 	if (!every) every++;}
	// An initial estimate will be printed before completion if the total estimated
	// time > threshold
	void set_threshold_for_completion_time_estimate(double threshold){
		threshold_completion_time_estimate = threshold;
	}
	void set_limit(unsigned e) {limit = e;}
	void use_timer() {using_timer = true;}
	void begin()
	{
		// not finished!
		if (count_dots)
			return;
		iteration = 0;
		iteration_in_interval = 0;
	}
	void finish()
	{
		// only print brackets if made progress (ie. some dots were printed)
		if (any_letters_printed())
		{
			os << "]";
			if (using_timer)
			{
				// if line_length = 50, count_dots = 50 then
				assert(line_length - ((count_letters - 1) % line_length) > 0);
				os	<< std::string(line_length - ((count_letters - 1) % line_length), ' ');
				elapsed_time += timer.reset();
				os << timer.pretty_time(elapsed_time);
				if (limit)
					os << "   altogether.";
			}
			os << std::endl;
		}

		elapsed_time = 0.0;
		count_dots = 0;
		count_letters = 0;
		//iteration = 0;
	}
	~t_progress_indicator()
	{
		// print closing bracket
		finish();
	}

	void print(const std::string& str)
	{
		for (unsigned i = 0; i < str.length(); ++i)
			print_letter(str[i]);
	}

	void print_letter(char c)
	{
		// print opening bracket if this is the first increment
		if (!any_letters_printed())
		{
			// print initial estimated time if required
			if (using_timer && limit && threshold_completion_time_estimate > 0.0)
			{
				elapsed_time += timer.reset();
				double forecast_time = elapsed_time * limit / (iteration + iteration_in_interval);
				if (forecast_time > threshold_completion_time_estimate)
					os	<< "Estimated to take " << timer.pretty_time(forecast_time) << "\n";
			}
			os << "\t[";
			os.flush();
		}

		// wrap around if necessary
		if (any_letters_printed() && count_letters % line_length == 0)
		{
			if (using_timer)
			{
				elapsed_time += timer.reset();
				os << "  " << timer.pretty_time(elapsed_time);
				if (limit)
				{
					double forecast_time = elapsed_time * limit / (iteration + iteration_in_interval);
					os	<< " ( out of " << timer.pretty_time(forecast_time) << ")";
				}
			}
			os << "\n\t ";
		}

		++count_letters;
		os << c;
		os.flush();
	}
	unsigned value(){return iteration + iteration_in_interval;}
	bool iterating() {return (iteration + iteration_in_interval) < limit;}
	void increment()
	{
		assert(every);
		if (iteration + iteration_in_interval == 0)
			timer.reset();
		if (++iteration_in_interval >= every)
			iterate_and_print();
	}
	void increment(	uint64_t file_size, 
					uint64_t bytes_read, 
					unsigned cnt_rows_read
					) 
	{
		assert(every);
		if (iteration + iteration_in_interval == 0)
			timer.reset();
		if (++iteration_in_interval >= every)
		{
			// estimate initial every. Don't worry if this means we do or do not have to
			// print a dot because every has changed:
			// iterate_and_print() will handle all eventualities...
			// I.e. 
			//	iteration_in_interval < every or iteration_in_interval >>>> every
			if (!count_dots)
				reestimate_parameters(	file_size,    
										bytes_read,   
										cnt_rows_read );
			iterate_and_print();
			reestimate_parameters(	file_size,    
									bytes_read,   
									cnt_rows_read );
		}
	}
	void operator++()
	{
		increment();
	}
	void operator++(int)
	{
		increment();
	}
	void operator+=(unsigned i)
	{
		if (!i)
			return;
		assert(every);
		if (iteration + iteration_in_interval == 0)
			timer.reset();

		iterate_and_print(i);
	}


	//
	//	print requisite number of dots, adjusting iteration and iteration in interval
	//
	void iterate_and_print(unsigned N = 0)
	{
		// add to iterations in this interval first
		iteration_in_interval += N;

		// number of dots to print
		unsigned num_of_dots_to_print = iteration_in_interval / every;
		iteration += every * num_of_dots_to_print;
		iteration_in_interval = iteration_in_interval % every;

		// add as many dots as necessary
		for (unsigned j = 0; j < num_of_dots_to_print; ++j)
		{
			print_letter(indicator);
			++count_dots;
		}
	}
};
class t_auto_progress_indicator
{
	t_progress_indicator* pdot;
public:
	t_auto_progress_indicator(t_progress_indicator& dot, bool on = true)
		:pdot(0)
	{
		if (on)
		{
			pdot = &dot;
			pdot->begin();
		}
	}
	~t_auto_progress_indicator()
	{
		if (pdot)
			pdot->finish();
	}
};
#endif	// PROGRESS_INDICATOR_H
